package com.hundun.vision.biz.core;

import org.ta4j.core.Position;
import org.ta4j.core.Trade;
import org.ta4j.core.Trade.TradeType;
import org.ta4j.core.TradingRecord;
import org.ta4j.core.analysis.cost.CostModel;
import org.ta4j.core.analysis.cost.ZeroCostModel;
import org.ta4j.core.num.DecimalNum;
import org.ta4j.core.num.Num;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author ：RuoChen
 * @date ：10:55 2024/12/12
 * @description：
 */
public class ContractTradingRecord implements TradingRecord {
    private String name;

    private final Integer startIndex;

    private final Integer endIndex;

    private final List<Trade> trades = new ArrayList<>();

    private final List<Trade> buyTrades = new ArrayList<>();

    private final List<Trade> sellTrades = new ArrayList<>();

    private final List<Trade> entryTrades = new ArrayList<>();

    private final List<Trade> exitTrades = new ArrayList<>();

    private final TradeType startingType;

    private final List<Position> positions = new ArrayList<>();

    private Position currentPosition;

    private final transient CostModel transactionCostModel;

    private final transient CostModel holdingCostModel;

    private final DecimalNum leverage;

    private final DecimalNum contractSize;

    public ContractTradingRecord() {
        this(TradeType.BUY, null, null, new ZeroCostModel(), new ZeroCostModel(), BigDecimal.valueOf(1), BigDecimal.valueOf(1));
    }

    public ContractTradingRecord(TradeType entryTradeType, CostModel transactionCostModel,
                                 CostModel holdingCostModel, BigDecimal leverage, BigDecimal contractSize) {
        this(entryTradeType, null, null, transactionCostModel, holdingCostModel, leverage, contractSize);
    }

    public ContractTradingRecord(TradeType entryTradeType, Integer startIndex, Integer endIndex,
                                 CostModel transactionCostModel, CostModel holdingCostModel,
                                 BigDecimal leverage, BigDecimal contractSize) {
        this.startingType = entryTradeType;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
        this.transactionCostModel = transactionCostModel;
        this.holdingCostModel = holdingCostModel;
        this.leverage = DecimalNum.valueOf(leverage);
        this.contractSize = DecimalNum.valueOf(contractSize);
        this.currentPosition = new Position(entryTradeType, transactionCostModel, holdingCostModel);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public TradeType getStartingType() {
        return startingType;
    }

    @Override
    public Position getCurrentPosition() {
        return currentPosition;
    }

    @Override
    public void operate(int index, Num price, Num amount) {
        if (currentPosition.isClosed()) {
            // Current position closed, should not occur
            throw new IllegalStateException("Current position should not be closed");
        }
        boolean newTradeWillBeAnEntry = currentPosition.isNew();
        Trade newTrade = currentPosition.operate(index, price, amount.multipliedBy(contractSize));
        recordTrade(newTrade, newTradeWillBeAnEntry);
    }

    @Override
    public boolean enter(int index, Num price, Num amount) {
        if (currentPosition.isNew()) {
            operate(index, price, amount);
            return true;
        }
        return false;
    }

    @Override
    public boolean exit(int index, Num price, Num amount) {
        if (currentPosition.isOpened()) {
            operate(index, price, amount);
            return true;
        }
        return false;
    }

    @Override
    public CostModel getTransactionCostModel() {
        return transactionCostModel;
    }

    @Override
    public CostModel getHoldingCostModel() {
        return holdingCostModel;
    }

    @Override
    public List<Position> getPositions() {
        return positions;
    }

    @Override
    public List<Trade> getTrades() {
        return trades;
    }

    @Override
    public Trade getLastTrade() {
        if (!trades.isEmpty()) {
            return trades.get(trades.size() - 1);
        }
        return null;
    }

    @Override
    public Trade getLastTrade(TradeType tradeType) {
        if (TradeType.BUY == tradeType && !buyTrades.isEmpty()) {
            return buyTrades.get(buyTrades.size() - 1);
        } else if (TradeType.SELL == tradeType && !sellTrades.isEmpty()) {
            return sellTrades.get(sellTrades.size() - 1);
        }
        return null;
    }

    @Override
    public Trade getLastEntry() {
        if (!entryTrades.isEmpty()) {
            return entryTrades.get(entryTrades.size() - 1);
        }
        return null;
    }

    @Override
    public Trade getLastExit() {
        if (!exitTrades.isEmpty()) {
            return exitTrades.get(exitTrades.size() - 1);
        }
        return null;
    }

    @Override
    public Integer getStartIndex() {
        return startIndex;
    }

    @Override
    public Integer getEndIndex() {
        return endIndex;
    }

    private void recordTrade(Trade trade, boolean isEntry) {
        Objects.requireNonNull(trade, "Trade should not be null");

        // Storing the new trade in entries/exits lists
        if (isEntry) {
            entryTrades.add(trade);
        } else {
            exitTrades.add(trade);
        }

        // Storing the new trade in trades list
        trades.add(trade);
        if (TradeType.BUY == trade.getType()) {
            // Storing the new trade in buy trades list
            buyTrades.add(trade);
        } else if (TradeType.SELL == trade.getType()) {
            // Storing the new trade in sell trades list
            sellTrades.add(trade);
        }

        // Storing the position if closed
        if (currentPosition.isClosed()) {
            positions.add(currentPosition);
            currentPosition = new Position(startingType, transactionCostModel, holdingCostModel);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder().append("ContractTradingRecord: ")
                .append(name == null ? "" : name)
                .append(System.lineSeparator());
        for (Trade trade : trades) {
            sb.append(trade.toString()).append(System.lineSeparator());
        }
        return sb.toString();
    }

    public Num getLeverage() {
        return leverage;
    }

    public Num getContractSize() {
        return contractSize;
    }
}